package com.imooc.ecommerce.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.imooc.ecommerce.common.TableId;
import com.imooc.ecommerce.constant.GoodsConstant;
import com.imooc.ecommerce.entity.EcommerceGoods;
import com.imooc.ecommerce.goods.DeductGoodsInventory;
import com.imooc.ecommerce.goods.GoodsInfo;
import com.imooc.ecommerce.goods.SimpleGoodsInfo;
import com.imooc.ecommerce.mapper.EcommerceGoodsMapper;
import com.imooc.ecommerce.service.IGoodsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 商品微服务相关功能实现
 *
 * @author zhangshao
 * @since 2023/12/1 21:17
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class GoodsServiceImpl implements IGoodsService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private EcommerceGoodsMapper goodsMapper;

    @Override
    public List<GoodsInfo> getGoodsInfoByTableId(TableId tableId) {

        // 详细的商品信息不能从redisCache中获取
        List<Long> ids = tableId.getIds().stream().map(TableId.Id::getId).collect(Collectors.toList());
        log.info("get goods info by ids: [{}]", JSON.toJSONString(ids));

        List<EcommerceGoods> ecommerceGoods = goodsMapper.selectBatchIds(ids);


        return ecommerceGoods.stream().map(EcommerceGoods::toGoodsInfo).collect(Collectors.toList());
    }

    @Override
    public IPage<SimpleGoodsInfo> getSimpleGoodsInfoByPage(int pageNum) {
        // 分页不能从redis cache中去拿
        if (pageNum <= 1) {
            //page默认是第一页
            pageNum = 1;
        }
        Page<EcommerceGoods> page = new Page<>(pageNum, 10);

        LambdaQueryWrapper<EcommerceGoods> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(EcommerceGoods::getId);

        List<EcommerceGoods> ecommerceGoods = goodsMapper.selectPage(page, queryWrapper).getRecords();
        List<SimpleGoodsInfo> simpleGoodsList = ecommerceGoods.stream().map(EcommerceGoods::toSimple).collect(Collectors.toList());
        Page<SimpleGoodsInfo> resPage = new Page<>(pageNum, 10);
        resPage.setRecords(simpleGoodsList);
        resPage.setTotal(simpleGoodsList.size());
        return resPage;
    }

    @Override
    public List<SimpleGoodsInfo> getSimpleGoodsInfoByTableId(TableId tableId) {
        //获取商品的简单信息,可以从redis cache中去拿，拿不到需要从DB中获取并保存到redis里面
        //redis中的kv都是字符串类型
        List<Object> goodIds = tableId.getIds().stream().map(i -> i.getId().toString()).collect(Collectors.toList());
        // FIXME 如果cache中查不到goodsId对应的数据,返回的是null,[null,null]
        List<Object> cachedSimpleGoodsInfos = redisTemplate.opsForHash().multiGet(GoodsConstant.ECOMMERCE_GOODS_DICT_KEY, goodIds).stream().filter(Objects::nonNull).collect(Collectors.toList());
        //如果从redis中查询到了商品信息，分两种情况去操作
        if (CollectionUtil.isNotEmpty(cachedSimpleGoodsInfos)) {
            //1. 如果从缓存中查询出所有需要的SimpleGoodsInfo
            if (cachedSimpleGoodsInfos.size() == goodIds.size()) {
                log.info("get Simple goods info by ids(from cache):[{}]", JSON.toJSONString(goodIds));
                return parseCachedGoodsInfo(cachedSimpleGoodsInfos);
            } else {
                //2. 一半从数据表中获取(right),一半从redis cache中获取(left)
                List<SimpleGoodsInfo> left = parseCachedGoodsInfo(cachedSimpleGoodsInfos);
                //取差集: 传递进来的参数 - 缓存中查到的= 缓存中还没有的
                Collection<Long> subtractIds = CollectionUtil.subtract(
                        goodIds.stream()
                                .map(g -> Long.valueOf(g.toString())).collect(Collectors.toList()),
                        left.stream().map(SimpleGoodsInfo::getId).collect(Collectors.toList())
                );
                //缓存中没有的，查询数据表并缓存
                List<SimpleGoodsInfo> right = queryGoodsFromDBAndCacheToRedis(new TableId(subtractIds.stream().map(TableId.Id::new).collect(Collectors.toList())));
                //合并left 和 right 并返回
                log.info("get simple goods info by id (from db and cache):[{}]", JSON.toJSONString(subtractIds));
                return new ArrayList<>(CollectionUtils.union(left, right));
            }
        } else {
            // 从redis里面什么都没有查到
            return queryGoodsFromDBAndCacheToRedis(tableId);
        }
    }

    /**
     * 将缓存中的数据反序列化成 Java Pojo对象
     *
     * @param cachedSimpleGoodsInfo
     * @return
     */
    private List<SimpleGoodsInfo> parseCachedGoodsInfo(List<Object> cachedSimpleGoodsInfo) {
        return cachedSimpleGoodsInfo.stream().map(s -> JSON.parseObject(s.toString(), SimpleGoodsInfo.class))
                .collect(Collectors.toList());
    }

    /**
     * 再次查询
     *
     * @param tableId
     * @return
     */
    private List<SimpleGoodsInfo> queryGoodsFromDBAndCacheToRedis(TableId tableId) {
        //从数据表中查询数据并做转换
        List<Long> ids = tableId.getIds().stream().map(TableId.Id::getId).collect(Collectors.toList());

        log.info("get simple goods info by ids(from db):[{}]", JSON.toJSONString(ids));
        List<EcommerceGoods> ecommerceGoods = goodsMapper.selectBatchIds(ids);
        List<SimpleGoodsInfo> result = ecommerceGoods.stream().map(EcommerceGoods::toSimple).collect(Collectors.toList());
        //将结果缓存,下一次可以直接从redisCache中查询
        log.info("cache goods info : [{}]", JSON.toJSONString(ids));

        Map<String, String> id2JsonObject = new HashMap<>(result.size());
        result.forEach(g -> id2JsonObject.put(
                g.getId().toString(), JSON.toJSONString(g)
        ));
        //保存到redis中
        redisTemplate.opsForHash().putAll(GoodsConstant.ECOMMERCE_GOODS_DICT_KEY, id2JsonObject);
        return result;
    }

    @Override
    public Boolean deductGoodsInventory(List<DeductGoodsInventory> deductGoodsInventories) {
        // 检验下参数是否合法
        deductGoodsInventories.forEach(d -> {
            if (d.getCount() <= 0) {
                throw new RuntimeException("purchase goods count need >0");
            }
        });
        List<EcommerceGoods> ecommerceGoods = goodsMapper.selectBatchIds(deductGoodsInventories.stream().map(DeductGoodsInventory::getGoodsId).collect(Collectors.toList()));
        // 根据查询的goodsIds查询不到商品对象,抛异常
        if(CollectionUtil.isEmpty(ecommerceGoods)){
            throw new RuntimeException("can not find any goods by request");
        }
        //查询出来的商品数量与传递的不一致,抛异常
        if(ecommerceGoods.size()!=deductGoodsInventories.size()){
            throw new RuntimeException("request is not valid");
        }
        // goodsId -> DeductGoodsInventory
        Map<Long,DeductGoodsInventory> goodsId2Inventory = deductGoodsInventories.stream().collect(Collectors.toMap(DeductGoodsInventory::getGoodsId, Function.identity()));

        //检查是不是可以扣减库存,再去扣减库存
        ecommerceGoods.forEach( g->{
            Long currentInventory = g.getInventory();
            Integer needDeductInventory = goodsId2Inventory.get(g.getId()).getCount();
            if(currentInventory < needDeductInventory){
                log.error("goods inventory is not enough :[{}],[{}]",currentInventory,needDeductInventory);
                throw new RuntimeException("goods inventory is not enough: "+g.getId());
            }
            //扣减库存
            g.setInventory(currentInventory - needDeductInventory);
            log.info("deduct goods inventor:[{}],[{},[{}]]",g.getId(),currentInventory,g.getInventory());
        });

        //执行更新库存数量
        for(EcommerceGoods goods:ecommerceGoods){
            goodsMapper.updateById(goods);
        }
        log.info("deduct goods inventory done.");
        return true;
    }
}
